﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PSE.Hosting.ServiceHoster.Logging;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Security;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Reflection;

namespace PSE.Hosting.ServiceHoster.Business
{
    [Serializable]
    public class Impersonation
    {
        #region Unmanaged dll methods declarations

        private enum SECURITY_IMPERSONATION_LEVEL : int
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3
        }

        // Attempts to log a user on to the local computer
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        //Closes open handles returned by LogonUser
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        // Creates duplicate token handle
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

        #endregion

        #region Constants

        private const int LOGON32_PROVIDER_DEFAULT = 0;
        private const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
        private const int LOGON32_LOGON_INTERACTIVE = 2;

        #endregion

        public Impersonation(string domain, string userName, string password)
        {
            _domain = domain;
            _userName = userName;
            _password = password;
        }

        private string _domain;
        /// <summary>
        /// 
        /// </summary>
        public string Domain
        {
            get { return _domain; }
            set { _domain = value; }
        }

        private string _userName;
        /// <summary>
        /// 
        /// </summary>
        public string UserName
        {
            get { return _userName; }
            set { _userName = value; }
        }

        private string _password;
        /// <summary>
        /// 
        /// </summary>
        public string Password
        {
            get { return _password; }
            set { _password = value; }
        }

        public void ValidateLogin()
        {
            IntPtr userTokenHandle = new IntPtr(0);
            userTokenHandle = IntPtr.Zero;

            try
            {
                // Get a handle to the user token
                if (!LogonUser(this.UserName, this.Domain, this.Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref userTokenHandle))
                {
                    throw new InvalidOperationException("Invalid user name or password.");
                }
            }
            finally
            {
                // Close handles
                if (userTokenHandle != IntPtr.Zero)
                    CloseHandle(userTokenHandle);
            }
        }

        public void Impersonate()
        {
            WindowsImpersonationContext impersonatedUser = null;
            string resultMessage = String.Empty;

            // if only domain name is blank, assume local machine
            if (string.IsNullOrEmpty(this.Domain) && !string.IsNullOrEmpty(this.UserName))
            {
                this.Domain = System.Environment.MachineName;
            }

            if (!string.IsNullOrEmpty(this.Domain) && !string.IsNullOrEmpty(this.UserName))
            {
                this.Domain = this.Domain.ToUpper();

                // Initialize tokens
                IntPtr userTokenHandle = new IntPtr(0);
                IntPtr duplicateTokenHandle = new IntPtr(0);
                userTokenHandle = IntPtr.Zero;
                duplicateTokenHandle = IntPtr.Zero;

                try
                {
                    // Get a handle to the user token
                    bool isValidLogin = LogonUser(this.UserName, this.Domain, this.Password,
                        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                            ref userTokenHandle);

                    if (!isValidLogin)
                    {
                        int errorCode = Marshal.GetLastWin32Error();
                        resultMessage = string.Format("Invalid user name or password: {0}\\{1}, Error Code: {2}", this.Domain, this.UserName, errorCode);

                        // Log the error
                        Logger.Log(resultMessage, Logger.EntryType.Error);
                        throw new InvalidOperationException(resultMessage);
                    }

                    // Duplicate the Token
                    bool isDuplicated = DuplicateToken(userTokenHandle, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
                        ref duplicateTokenHandle);

                    // Did DuplicateToken fail?
                    if (!isDuplicated)
                    {
                        int errorCode = Marshal.GetLastWin32Error();
                        // Close existing handle
                        CloseHandle(userTokenHandle);
                        resultMessage = "DuplicateToken() failed with error code: "
                            + errorCode + "\r\n";

                        /// Log the error
                        Logger.Log(resultMessage, Logger.EntryType.Error);
                        throw new SecurityException(resultMessage);
                    }
                    else
                    {
                        // Create an identity using the duplicated token
                        using (WindowsIdentity identity = new WindowsIdentity(duplicateTokenHandle))
                        {
                            impersonatedUser = identity.Impersonate();
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log(ex, Logger.EntryType.Error);
                    throw;
                }
                finally
                {
                    // Close handles
                    if (userTokenHandle != IntPtr.Zero)
                        CloseHandle(userTokenHandle);
                    if (duplicateTokenHandle != IntPtr.Zero)
                        CloseHandle(duplicateTokenHandle);
                }
            }

            Logger.Log(resultMessage, Logger.EntryType.Information);
        }

    }


}
